﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.Internals;

namespace NTasking
{

    /// <summary>
    /// Define extension methods which allow to implement the fluent syntax for writing a selective accept
    /// </summary>
    public static partial class SelectiveAcceptEx
    {

        #region Implementation of IRelativeDelay

        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="duration">The delay duration.</param>
        /// <returns></returns>
        public static SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> Delay(this SelectiveAccept.IDelay<SelectiveAccept.IRelativeDelay> delay, TimeSpan duration)
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)delay).Select;

            Timeout timeout = new Timeout(duration) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> { Select = s };
        }


        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="milliseconds">The delay duration in milliseconds.</param>
        /// <returns></returns>
        public static SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> Delay(this SelectiveAccept.IDelay<SelectiveAccept.IRelativeDelay> delay, int milliseconds)
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)delay).Select;

            Timeout timeout = new Timeout(milliseconds) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> { Select = s };
        }


        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="seconds">The delay duration in seconds.</param>
        /// <returns></returns>
        public static SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> Delay(this SelectiveAccept.IDelay<SelectiveAccept.IRelativeDelay> delay, double seconds)
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)delay).Select;

            Timeout timeout = new Timeout(seconds) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new SelectiveAccept.AfterDelay<SelectiveAccept.IRelativeDelay> { Select = s };
        }

        #endregion


        #region Implementation of IAbsoluteDelay

        /// <summary>
        /// Add a delay alternative.
        /// </summary>
        /// <param name="delay">The current instance</param>
        /// <param name="date">The delay end.</param>
        /// <returns></returns>
        public static SelectiveAccept.AfterDelay<SelectiveAccept.IAbsoluteDelay> Delay(this SelectiveAccept.IDelay<SelectiveAccept.IAbsoluteDelay> delay, DateTime date)
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)delay).Select;

            Timeout timeout = new Timeout(date) { Barrier = s.CurrentSimpleBarrier };

            if (timeout.IsOpened() && s.Timeout.IsLater(timeout))
                s.Timeout = timeout;

            s.CurrentContinuable = timeout;

            return new SelectiveAccept.AfterDelay<SelectiveAccept.IAbsoluteDelay> { Select = s };
        }

        #endregion


        #region Implementation of IBeforeThen

        /// <summary>
        /// Add continuation statements.
        /// </summary>
        /// <typeparam name="T">The type returned</typeparam>
        /// <param name="iba">The current instance</param>
        /// <param name="continuation">The continuation statements.</param>
        /// <returns></returns>
        public static T Then<T>(this SelectiveAccept.IBeforeThen<T> iba, Action continuation)
            where T : SelectiveAcceptSyntaxNode, new()
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)iba).Select;

            s.CurrentContinuable.Continuation = continuation;

            return new T { Select = s };
        }

        #endregion


        #region Implementation of IBeforeWhen<T>

        /// <summary>
        /// Add a simple barrier.
        /// </summary>
        /// <typeparam name="T">The return type</typeparam>
        /// <param name="ibw">The current instance</param>
        /// <param name="barrier">The barrier.</param>
        /// <returns></returns>
        public static T When<T>(this SelectiveAccept.IBeforeWhen<T> ibw, Func<bool> barrier)
            where T : SelectiveAcceptSyntaxNode, new()
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)ibw).Select;

            //if (barrier == null)
            //{
            //    s.Task.OnSelectSyntaxError();
            //    throw Errors.BarrierIsNull();
            //}

            s.CurrentSimpleBarrier = barrier;

            return new T { Select = s };
        }

        #endregion


        #region Implementation of IBeforeIndexedWhen<T, IndexT>

        /// <summary>
        /// Add an indexed barrier.
        /// </summary>
        /// <typeparam name="IndexT">The type of the index.</typeparam>
        /// <typeparam name="T">The return type</typeparam>
        /// <param name="ibiw">The current instance</param>
        /// <param name="indexedBarrier">The indexed barrier.</param>
        /// <returns></returns>
        public static SelectiveAccept.BeforeAccept<IndexT, T> When<IndexT, T>(this SelectiveAccept.IBeforeIndexedWhen<T> ibiw, Func<IndexT, bool> indexedBarrier)
            where T : SelectiveAcceptSyntaxNode, new()
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)ibiw).Select;

            //if (indexedBarrier == null)
            //{
            //    s.Task.OnSelectSyntaxError();
            //    throw Errors.BarrierIsNull();
            //}

            s.SetCurrentIndexedBarrier(indexedBarrier);

            return new SelectiveAccept.BeforeAccept<IndexT, T> { Select = s };
        }

        #endregion


        #region Implementation of IBeforeElse

        /// <summary>
        /// Add an else alternative.
        /// </summary>
        /// <param name="ibe">The current instance</param>
        /// <param name="elseAlternative">The else alternative.</param>
        /// <returns></returns>
        public static SelectiveAccept.AfterElse Else(this SelectiveAccept.IBeforeElse ibe, Action elseAlternative)
        {
            SelectiveAccept s = ((SelectiveAcceptSyntaxNode)ibe).Select;

            if (elseAlternative == null)
                s.Else = () => { };
            else
                s.Else = elseAlternative;

            return new SelectiveAccept.AfterElse { Select = s };
        }

        #endregion


        #region Helper methods for IBEforeAccept implementation

        // TODO: comment all the following methods
        private static MethodAccept AcceptAny<ExecuteDelegateT>(SelectiveAccept s, IExecutable executable, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where ExecuteDelegateT : class
        {
            // Check that the select is not within the abortable part of a selective call
            if (ThreadWrapper.CurrentThread.InAbortablePart)
                throw Errors.AcceptNotAllowedInAbortablePart(Task.CurrentTask);

            if (checkBody && body == null)
                throw Errors.BodyIsNull(executable);

            // ReSharper disable PossibleNullReferenceException
            if (body != null && (body as MulticastDelegate).Target as IExecutable != null)
                throw Errors.AcceptBodyIsSynchronized(s.Task, (body as MulticastDelegate).Target as SynchronizedMethod);
            // ReSharper restore PossibleNullReferenceException

            if (executable.Family.CurrentlyBeingAccepted)
                throw Errors.InvalidNestedAccept(s.Task, executable.Family);

            if (executable.Family.Owner != s.Task)
                throw Errors.AcceptCallNotOwned(executable.Family);

            MethodAccept context = executable.CreateAccept();
            context.ExceptionHandler = exceptionHandler;
            context.Barrier = s.CurrentSimpleBarrier;

            s.CurrentContinuable = context;

            if (!context.IsClosed)
            {
                s.OpenEntries += 1;
                context.Target.AcceptableCalls.Add(context);
            }

            if (body != null)
                ((MethodAccept<ExecuteDelegateT>)context).Body = body;

            return context;
        }


        internal static MethodAccept AcceptEntry<CallDelegateT>(SelectiveAccept s, CallDelegateT call, CallDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
        {
            if (call == null)
                throw Errors.EntryIsNull("Accept");

            // ReSharper disable PossibleNullReferenceException
            var executable = (call as MulticastDelegate).Target as IExecutable;
            // ReSharper restore PossibleNullReferenceException

            if (executable == null)
                // ReSharper disable PossibleNullReferenceException
                throw Errors.AcceptCallNotSynchronized(s.Task, (call as MulticastDelegate).Target);
            // ReSharper restore PossibleNullReferenceException

            return AcceptAny(s, executable, body, checkBody, exceptionHandler);
        }


        private static T AcceptEntry<T, CallDelegateT>(SelectiveAccept s, CallDelegateT call, CallDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : SelectiveAcceptSyntaxNode, new()
            where CallDelegateT : class
        {
            AcceptEntry(s, call, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }


        internal static MethodAccept AcceptFamily<IndexT, ExecuteDelegateT>(SelectiveAccept s, IExecutable family, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where ExecuteDelegateT : class
        {
            if (family == null)
                throw Errors.EntryIsNull("Accept");

            MethodAccept accept = AcceptAny(s, family, body, checkBody, exceptionHandler);

            ((IGuardable<IndexT>)accept).IndexedBarrier = s.GetCurrentIndexedBarrier<IndexT>();

            return accept;
        }


        private static T AcceptFamily<T, IndexT, ExecuteDelegateT>(SelectiveAccept s, IExecutable family, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : SelectiveAcceptSyntaxNode, new()
            where ExecuteDelegateT : class
        {
            AcceptFamily<IndexT, ExecuteDelegateT>(s, family, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }


        internal static MethodAccept AcceptMember<IndexT, CallDelegateT, ExecuteDelegateT>(SelectiveAccept s, CallDelegateT call, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
            where ExecuteDelegateT : class
        {
            if (call == null)
                throw Errors.EntryIsNull("Accept");

            // ReSharper disable PossibleNullReferenceException
            var executable1 = (call as MulticastDelegate).Target as IExecutable;
            // ReSharper restore PossibleNullReferenceException

            // Check that the provided entry is an executable entity (entry or entry family member)
            if (executable1 == null)
                // ReSharper disable PossibleNullReferenceException
                throw Errors.AcceptCallNotSynchronized(s.Task, (call as MulticastDelegate).Target);
            // ReSharper restore PossibleNullReferenceException

            // ReSharper disable PossibleNullReferenceException
            var executable2 = (call as MulticastDelegate).Target as IExecutable<ExecuteDelegateT>;
            // ReSharper restore PossibleNullReferenceException

            // Check that the provided entry is an executable entity with correct body profile
            if (executable2 == null)
                throw Errors.IndexTypeMismatch(s.Task, executable1.Family);

            MethodAccept accept = AcceptAny(s, executable1, body, checkBody, exceptionHandler);

            ((IGuardable<IndexT>)accept).IndexedBarrier = s.GetCurrentIndexedBarrier<IndexT>();

            return accept;
        }


        private static T AcceptMember<T, IndexT, CallDelegateT, ExecuteDelegateT>(SelectiveAccept s, CallDelegateT call, ExecuteDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where T : SelectiveAcceptSyntaxNode, new()
            where CallDelegateT : class
            where ExecuteDelegateT : class
        {
            AcceptMember<IndexT, CallDelegateT, ExecuteDelegateT>(s, call, body, checkBody, exceptionHandler);
            return new T { Select = s };
        }

        #endregion

    }

}
